﻿using System;
using System.Text;
using System.Text.RegularExpressions;

namespace Quail.Helpers
{
    /// <summary>
    /// Some useful string extensions
    /// </summary>
    public static class StringExtensions
    {
        /// <summary>
        /// Parses an integer from a string even when there are non-digit characters surrounding the digits.  eg: Found 98 jobs! will return 98
        /// </summary>
        /// <param name="str">the string to parse: </param>
        /// <returns>the integer value found</returns>
        public static int ParseInt(this string str)
        {
            var result = SafeParseInt(str);

            if (result.HasValue)
                return result.Value;

            throw new Exception("Unable to find an integer value in the given string: " + str);
        }

        /// <summary>
        /// Parses an integer from a string even when there are non-digit characters surrounding the digits.  eg: Found 98 jobs! will return 98
        /// </summary>
        /// <param name="str">the string to parse: </param>
        /// <returns>the integer value found, or null if the string doesn't contain an integer value or an error occurs</returns>
        public static int? SafeParseInt(this string str)
        {
            string digits = string.Empty;
            bool founddigits = false;

            foreach (char c in str)
            {
                if (char.IsDigit(c))
                {
                    founddigits = true;
                    digits += c;
                }
                else if (founddigits)
                {
                    return int.Parse(digits);
                }
            }

            if (digits.Length > 0)
                return int.Parse(digits);

            return null;
        }

        /// <summary>
        /// Parses a double/float from a string even when there are non-digit characters surrounding the digits.  eg: Found $135.20 jobs! will return 135.20
        /// </summary>
        /// <param name="str">the string to parse</param>
        /// <returns>the double value found</returns>
        public static double ParseDouble(this string str)
        {
            var result = SafeParseDouble(str);

            if (result.HasValue)
                return result.Value;

            throw new Exception("Unable to find an double value in the given string: " + str);
        }

        /// <summary>
        /// Parses a double/float from a string even when there are non-digit characters surrounding the digits.  eg: Found $135.20 jobs! will return 135.20
        /// </summary>
        /// <param name="str">the string to parse</param>
        /// <returns>the double value found, or null if the string doesn't contain an integer value or an error occurs</returns>
        public static double? SafeParseDouble(this string str)
        {
            var digits = string.Empty;
            var founddigits = false;
            var founddot = false;

            foreach (var c in str)
            {
                if (char.IsDigit(c))
                {
                    founddigits = true;
                    digits += c;
                }
                else if (founddigits)
                {
                    if (c == '.' && !founddot)
                    {
                        founddot = true;
                        digits += c;
                    }
                    else
                        return double.Parse(digits);
                }
            }

            if (digits.Length > 0)
                return double.Parse(digits);

            return null;
        }

        /// <summary>
        /// Parses a date from a string even when there are non-digit characters surrounding the digits.  eg: "Accessed: 10/5/2010" would return #10/5/2010#
        /// </summary>
        /// <param name="str">the string to parse</param>
        /// <returns>the date value found</returns>
        public static DateTime ParseDate(this string str)
        {
            var result = SafeParseDate(str);

            if (result.HasValue)
                return result.Value;

            throw new Exception("Unable to find an datetime value in the given string: " + str);
        }
        
        /// <summary>
        /// Parses a date from a string even when there are non-digit characters surrounding the digits.  eg: "Accessed: 10/5/2010" would return #10/5/2010#
        /// </summary>
        /// <param name="str">the string to parse</param>
        /// <returns>the date value found, or null if the string doesn't contain an integer value or an error occurs</returns>
        public static DateTime? SafeParseDate(this string str)
        {
            var digits = string.Empty;
            var founddigits = false;
            var slashcount = 0;

            foreach (var c in str)
            {
                if (char.IsDigit(c))
                {
                    founddigits = true;
                    digits += c;
                }
                else if (founddigits)
                {
                    if ((c == '/' || c == '-') && slashcount < 2)
                    {
                        slashcount++;
                        digits += c;
                    }
                    else
                        return DateTime.Parse(digits);
                }
            }

            if (digits.Length > 0)
                return DateTime.Parse(digits);

            return null;
        }

        /// <summary>
        /// Produce an XPath literal equal to the value if possible; if not, produce
        /// an XPath expression that will match the value.
        /// 
        /// Note that this function will produce very long XPath expressions if a value
        /// contains a long run of double quotes.
        /// </summary>
        /// <param name="value">The value to match.</param>
        /// <returns>If the value contains only single or double quotes, an XPath
        /// literal equal to the value.  If it contains both, an XPath expression,
        /// using concat(), that evaluates to the value.</returns>
        /// <remarks>See: http://stackoverflow.com/questions/1341847/special-character-in-xpath-query
        /// </remarks>
        public static string AsXPathLiteral(this string value)
        {
            // if the value contains only single or double quotes, construct
            // an XPath literal
            if (!value.Contains("\""))
            {
                return "\"" + value + "\"";
            }
            if (!value.Contains("'"))
            {
                return "'" + value + "'";
            }

            // if the value contains both single and double quotes, construct an
            // expression that concatenates all non-double-quote substrings with
            // the quotes, e.g.:
            //
            //    concat("foo", '"', "bar")
            var sb = new StringBuilder();
            sb.Append("concat(");
            var substrings = value.Split('\"');
            for (var i = 0; i < substrings.Length; i++)
            {
                var needComma = (i > 0);
                if (substrings[i] != "")
                {
                    if (i > 0)
                    {
                        sb.Append(", ");
                    }
                    sb.Append("\"");
                    sb.Append(substrings[i]);
                    sb.Append("\"");
                    needComma = true;
                }

                if (i < substrings.Length - 1)
                {
                    if (needComma)
                    {
                        sb.Append(", ");
                    }
                    sb.Append("'\"'");
                }

            }
            sb.Append(")");
            return sb.ToString();
        }

        /// <summary>
        /// Removes html tags from a string (fairly simply - is not perfect)
        /// </summary>
        /// <param name="str">The string</param>
        /// <returns>The string without (most) HTML tags</returns>
        public static string RemoveHTMLTags(this string str)
        {
            return Regex.Replace(str, @"<(.|\n)*?>", string.Empty);
        }

        /// <summary>
        /// Replaces 2 or more spaces with a single one
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static string ReplaceMultipleSpacesWithSingle(this string val)
        {
            return val.ReplaceMultipleXWithSingle(' ');
        }

        /// <summary>
        /// Replaces multiple of some character with a single instance of it
        /// </summary>
        /// <param name="val">the string</param>
        /// <param name="c">char to replace</param>
        /// <returns></returns>
        public static string ReplaceMultipleXWithSingle(this string val, char c)
        {
            var replace = new Regex("[" + c + "]{2,}");
            val = replace.Replace(val, c.ToString());
            return val;
        }

        /// <summary>
        /// Returns the text between two strings to match
        /// </summary>
        /// <param name="val"></param>
        /// <param name="firstMatch"></param>
        /// <param name="nextMatch"></param>
        /// <returns></returns>
        public static string GetTextBetween(this string val, string firstMatch, string nextMatch)
        {
            var idx = val.IndexOf(firstMatch);
            if (idx != -1 && (idx + firstMatch.Length) < val.Length)
            {
                var tval = val.Substring(idx + firstMatch.Length);
                idx = tval.IndexOf(nextMatch);
                if (idx != -1)
                {
                    val = tval.Substring(0, idx);
                }
            }

            return val;
        }

        /// <summary>
        /// Replaces characters so that it can be used in a database query
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static string PrepForQuery(this string val)
        {
            return val.Replace("'", "''");
        }

        /// <summary>
        /// Returns the string found after the string to lookFor
        /// </summary>
        /// <param name="val">The string being searched</param>
        /// <param name="lookFor">The string to search for</param>
        /// <returns>The contents of the input string after the lookFor content</returns>
        public static string SubstringAfter(this string val, string lookFor)
        {
            return val.Substring(val.IndexOf(lookFor) + lookFor.Length);
        }

        /// <summary>
        /// Returns the string found before the string to lookFor
        /// </summary>
        /// <param name="val">The string being searched</param>
        /// <param name="lookFor">The string to search for</param>
        /// <returns>The contents of the input string from the start to the lookFor content</returns>
        public static string SubstringBefore(this string val, string lookFor)
        {
            return val.Substring(0, val.IndexOf(lookFor));
        }
    }
}
